package p08_bean的scope;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import p08_bean的scope.不同scope的bean.ApplicationBean;
import p08_bean的scope.不同scope的bean.PrototypeBean;
import p08_bean的scope.不同scope的bean.RequestBean;
import p08_bean的scope.不同scope的bean.SessionBean;

/**
 * bean的五种作用域：
 *  1.singleton:单例，默认的作用域，前面演示的都是这种作用域的bean
 *  2.prototype：多例，每次从容器中获取的都是新的bean对象
 *  3.request：请求域，在相同请求获取是同一个bean，生命周期跟请求的生命周期一样
 *  4.session：会话域，在相同会话获取是同一个bean
 *  5.application：web应用程序域，在同一个web应用内部，获取的都是同一个bean
 */
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class ScopeApplication {
    public static void main(String[] args) {
        SpringApplication.run(ScopeApplication.class);
    }

    /**
     * 在单例bean中注入多例bean，不能直接注入(因为单例bean只有一个，一旦注入多例bean，后面单例bean持有的一直是这个多例bean)，需要采用四种方式
     *  1.@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
     *      proxyMode指定注入的是代理bean，每次访问代理bean的方法，代理bean都会创建一个目标bean(根据bean的scope)，然后间接调用目标bean的同名方法
     *  2.@Lazy
     *      注入时，加上该注解，表示注入的是代理bean,原理跟1类似
     *  3.ObjectFactory
     *      通过该类包装，注入时，注入的是对象工厂，调用该对象的getObject方法会返回目标bean
     *      这种没有经过代理，效率更高
     *  4.context.getBean(ApplicationBean.class)
     *      注入容器，直接从容器中获取
     *      这种没有经过代理，效率更高
     *  这四种在单例bean里面得到非单例bean，都是用的延迟注入的思想
     *  下面这个单例bean(ScopeController)里，演示了如何使用四种非单例bean的方式
     */
    @RestController
    static class ScopeController {
        /**
         * 定义bean时，使用@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)指定
         * 这样注入的requestBean就是一个代理bean，每次获取，该代理bean就会返回一个新的目标bean
         */
        @Autowired
        private RequestBean requestBean;
        /**
         * 这种方式跟上面的等价，都是注入的SessionBean代理子类
         */
        @Lazy
        @Autowired
        private SessionBean sessionBean;

        /**
         * 直接从容器中获取bean，使用的是非代理
         */
        @Autowired
        private ApplicationContext context;
        /**
         * 有工厂每次返回一个新的对象，使用的是非代理
         * scope为prototype的bean，每次拿到的都是新对象，但是这种bean似乎没有被销毁
         */
        @Autowired
        private ObjectFactory<PrototypeBean> prototypeBean;

        @GetMapping("/testScope")
        public String test() {
            return "request ===> " + requestBean + "<hr>" +
                    "session ===> " + sessionBean + "<hr>" +
                    "application ===> " + context.getBean(ApplicationBean.class) + "<hr>" +
                    "prototype ===> " + prototypeBean.getObject() + "<hr>";
        }

    }
}
